/*
NoBuffer Notepad Copyright (C) 2015  Mahmoud Jaoune

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "filethread.h"

/*
 * Initialize the file thread
 */
NBN_FileThread::NBN_FileThread(QWidget *parent, QString fileName, QString currentTab, NBN_TextEdit *textEdit)
{
    setParent(parent);
    currentFile = new QFile();
    currentFile->setFileName(fileName);
    currentFile->open(QFile::ReadOnly);
    loadedTab = currentTab;
    currentTextEdit = textEdit;

    QSettings settings("settings.ini", QSettings::IniFormat);
    settings.beginGroup("Options");
    blockSize = settings.value("blockSize").toLongLong();
    settings.endGroup();

    if(blockSize == NULL) blockSize = BLOCKSIZE;
}

/*
 * Running the thread when using start()
 * This will seperate files into blocks in case the filesize was larger than blocksize
 */
void NBN_FileThread::run()
{
    nBlocks = 1;

    if(currentFile->size() >= blockSize)
    {
        nBlocks = currentFile->size() / blockSize;
        NBN_GetBlockFromFile(0, blockSize, blockSize, currentFile);
    }
    else NBN_GetBlockFromFile(0, currentFile->size(), currentFile->size(), currentFile);

    emit NBN_NumberOfBlocks(nBlocks, loadedTab);
}

/*
 * Loads a certain block of file
 */
void NBN_FileThread::NBN_LoadBlock(unsigned int index)
{
    NBN_GetBlockFromFile((index - 1) * blockSize, blockSize, blockSize, currentFile);
}

/*
 * Returns character at index of buffer given
 */
QChar NBN_FileThread::NBN_GetCharAt(int index, QString buffer)
{
    return buffer.at(index);
}

/*
 * Load block with specific size from file starting at a certain index
 * A maximum allocation size can be specified
 */
QString NBN_FileThread::NBN_GetBlockFromFile(unsigned long long offset, size_t blockSize, size_t allocSize, QFile *fileIO)
{
    emit NBN_SendClearText();

    QString block;

    //If bad memory sizes detected, return an empty string
    if((offset + blockSize) > fileIO->size() || (offset + allocSize) > fileIO->size() || (blockSize > allocSize))
    {
        block.push_back((QChar)0x00);
        return block;
    }

    //If blockSize is not that big, load it normally
    if(blockSize == fileIO->size())
    {
        block.push_back(fileIO->readAll());
        emit NBN_BufferReady(block);

        emit NBN_FileLoadFinished();
        return block;
    }

    QByteArray fileData;

    //Set file cursor to offset specified
    if(fileIO->seek(offset))
    {
        fileData = fileIO->read(allocSize);
    }
    else offset = 0;

    int c = 0;
    int d = 0;

    //Decide the number of times to chop the data in order to send it to the GUI TextEdit
    int bufferChopSize = blockSize; //We set a value just to stay away from bugs in-case a very random blockSize was defined
    //The chop size is determined by if the first number (after 24 to prevent system from hanging a lot) that blockSize can be divided by without carry
    for(unsigned int j = 24; j <= blockSize; ++j)
    {
        if((blockSize % j) == 0)
        {
            bufferChopSize = blockSize / j;
            break;
        }
    }

    for(unsigned int i = 0; i < blockSize; ++i)
    {
        //If the index is larger than file original size, stop the loop (Nothing to read)
        if((i + offset) >= fileIO->size())
            break;

        ++c;
        ++d;

        //Update file process GUI whenever a "chop size" gets loaded
        if(d == bufferChopSize)
        {
            emit NBN_FileLoadProcess(i + 1, blockSize);
            d = 0;
        }

        //Push data to the block that is going to be loaded
        block.push_back(fileData.at(i));

        //Whenever "chop size" is reached, send piece of data (Loaded latest) to TextEdit so it can be loaded in stages
        if(c == bufferChopSize)
        {
            emit NBN_BufferReady(block.mid(i - bufferChopSize, bufferChopSize));
            c = 0;
            //usleep(100000);
        }
    }

    //File loading has finished
    emit NBN_FileLoadFinished();

    return block;
}

/*
 * Get current line number by counting countered \n escaping characters
 */
int NBN_FileThread::NBN_GetCurrentLineNumber(int reachedSize, QFile *fileIO)
{
    int lineCount = 1;
    size_t blockSize = reachedSize + 1; //We don't want to count the escaping chars in the whole file

    fileIO->seek(0);
    QByteArray fileData = fileIO->read(blockSize);

    //If bad memory size detected, return 0
    if(blockSize > fileIO->size()) return 0;

    for(unsigned int i = 0; i < blockSize; ++i)
    {
        if(NBN_GetCharAt(i, QString(fileData)) == '\n')
        {
            ++lineCount;
        }
    }

    return lineCount;
}

/*
 * Terminate thread when file tab gets closed and then close file
 */
void NBN_FileThread::NBN_CloseThread(QString currentTab)
{
    if(currentTab == this->loadedTab)
    {
        this->terminate();
        delete this;

        currentFile->close();
        delete currentFile;
    }
}

/*
 * Saves the block loaded into the whole file
 */
void NBN_FileThread::NBN_SaveFile(QString fileName, BlockDB database)
{
    //Comparing signaled fileName to the thread's loaded filename will let only the thread targetted function the process
    if(fileName == currentFile->fileName())
    {
        QFile saveFile(fileName);
        saveFile.open(QFile::ReadWrite);

        int seekPos = (database.currentBlock - 1) * blockSize;
        bool status = saveFile.seek(seekPos);

        if(status == true)
        {
            saveFile.write(currentTextEdit->toPlainText().toStdString().c_str());

            QStatusBar * statusBar = (QStatusBar *)currentTextEdit->parent()->parent()->parent()->parent()->findChild<QStatusBar *>("statusBar");
            statusBar->showMessage("File saved", 3000);
        }
        else
        {
            QMessageBox saveFailed;
            saveFailed.setWindowTitle("Failed to save file!");
            saveFailed.setText("File was not saved, please check if the file was modified from an outer source or removed.");
            saveFailed.setIcon(QMessageBox::Critical);
            saveFailed.exec();
        }

        saveFile.close();
    }
}

/*
 * Same as above function but saves the file into a new file
 */
void NBN_FileThread::NBN_SaveAsFile(QString fileName, QString newFileName, BlockDB database)
{
    if(fileName == currentFile->fileName())
    {
        QFile saveFile(newFileName);
        saveFile.open(QFile::ReadWrite | QFile::Truncate);

        bool status = saveFile.seek(0);

        if(status == true)
        {
            saveFile.write(currentTextEdit->toPlainText().toStdString().c_str());

            QStatusBar * statusBar = (QStatusBar *)currentTextEdit->parent()->parent()->parent()->parent()->findChild<QStatusBar *>("statusBar");
            statusBar->showMessage("File saved", 3000);
        }
        else
        {
            QMessageBox saveFailed;
            saveFailed.setWindowTitle("Failed to save file!");
            saveFailed.setText("File was not saved, please check if the file was modified from an outer source or removed.");
            saveFailed.setIcon(QMessageBox::Critical);
            saveFailed.exec();
        }

        saveFile.close();
    }
}

/*
 * Text editing-related functions
 */
void NBN_FileThread::on_UndoAction(QString fileName)
{
    if(fileName == currentFile->fileName())
    {
        currentTextEdit->undo();
    }
}

void NBN_FileThread::on_RedoAction(QString fileName)
{
    if(fileName == currentFile->fileName())
    {
        currentTextEdit->redo();
    }
}

void NBN_FileThread::on_CutAction(QString fileName)
{
    if(fileName == currentFile->fileName())
    {
        currentTextEdit->cut();
    }
}

void NBN_FileThread::on_CopyAction(QString fileName)
{
    if(fileName == currentFile->fileName())
    {
        currentTextEdit->copy();
    }
}

void NBN_FileThread::on_PasteAction(QString fileName)
{
    if(fileName == currentFile->fileName())
    {
        currentTextEdit->paste();
    }
}
//End of text editing-related functions
